luci-base: add `responseProgress` callback and `stderr` option
authorRichard Yu <[email protected]>
Mon, 1 Sep 2025 14:39:47 +0000 (22:39 +0800)
committerPaul Donald <[email protected]>
Fri, 12 Dec 2025 15:39:55 +0000 (16:39 +0100)
for LuCI.request and `fs.exec_direct()`.

This commit adds XHR response progress event option in luci.js and fs.js.
And diagnostics.js will use this new event to update command output.

This commit also adds a `stderr` option in `fs.exec_direct()`, and
requires a patch for `cgi-io`.

Currently `cgi-io` redirects stderr to /dev/null, which makes user
unable to see error messages. The patch adds a `stderr` option,
and if it's set to `1`, redirects stderr to stdout.

Signed-off-by: Richard Yu <[email protected]>
Link: https://github.com/openwrt/luci/pull/7920
modules/luci-base/htdocs/luci-static/resources/fs.js
modules/luci-base/htdocs/luci-static/resources/luci.js

index cf64cfdf62e73ebef58987fa4a3c5d9157edd498..3e9326ee1ff5d25e4c9b779e1fc3dad7a0b719eb 100644 (file)
@@ -397,12 +397,22 @@ var FileSystem = baseclass.extend(/** @lends LuCI.fs.prototype */ {
         * is usually not needed but can be useful for programs that cannot
         * handle UTF-8 input.
         *
+        * @param {boolean} [stderr=false]
+        * Whether to include stderr output in command output. This is usually
+        * not needed but can be useful to execute a command and get full output.
+        *
+        * @param {function} [responseProgress=null]
+        * An optional request callback function which receives ProgressEvent
+        * instances as sole argument during the HTTP response transfer. This is
+        * usually not needed but can be useful to receive realtime command
+        * output before command exit.
+        *
         * @returns {Promise<*>}
         * Returns a promise resolving with the command stdout output interpreted
         * according to the specified type or rejecting with an error stating the
         * failure reason.
         */
-       exec_direct: function(command, params, type, latin1) {
+       exec_direct: function(command, params, type, latin1, stderr, responseProgress) {
                var cmdstr = String(command)
                        .replace(/\\/g, '\\\\').replace(/(\s)/g, '\\$1');
 
@@ -416,12 +426,12 @@ var FileSystem = baseclass.extend(/** @lends LuCI.fs.prototype */ {
                else
                        cmdstr = encodeURIComponent(cmdstr);
 
-               var postdata = 'sessionid=%s&command=%s'
-                       .format(encodeURIComponent(L.env.sessionid), cmdstr);
+               var postdata = 'sessionid=%s&command=%s&stderr=%d'
+                       .format(encodeURIComponent(L.env.sessionid), cmdstr, stderr ? 1 : 0);
 
                return request.post(L.env.cgi_base + '/cgi-exec', postdata, {
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
-                       responseType: (type == 'blob') ? 'blob' : 'text'
+                       responseType: (type == 'blob') ? 'blob' : 'text', responseProgress
                }).then(handleCgiIoReply.bind({ type: type }));
        }
 });
index 9cb8cefe5e35765b309d53847b316f956d08d90c..9f3b88302aeb5c5c2605b2a2266e33cdae582abc 100644 (file)
                 * @property {function} [progress]
                 * An optional request callback function which receives ProgressEvent
                 * instances as sole argument during the HTTP request transfer.
+                *
+                * @property {function} [responseProgress]
+                * An optional request callback function which receives ProgressEvent
+                * instances as sole argument during the HTTP response transfer.
                 */
 
                /**
                                        if ('progress' in opt && 'upload' in opt.xhr)
                                                opt.xhr.upload.addEventListener('progress', opt.progress);
 
+                                       if (opt.responseProgress != null)
+                                               opt.xhr.addEventListener('progress', opt.responseProgress);
+
                                        if (contenttype != null)
                                                opt.xhr.setRequestHeader('Content-Type', contenttype);